﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using log4net;

namespace Eff.IO {
    /// <summary>
    /// FileSystemWatch类的封装，
    /// 用于监测文件变化。
    /// 通过Timer避免重复事件冒泡。
    /// </summary>
    public sealed class LazyFileSystemWatcher {
        #region Delegates

        /// <summary>
        /// Changed事件回调声明
        /// </summary>
        public delegate void Handler(LazyFileSystemWatcher sender, EventArgument e);

        #endregion

        private readonly object _lockObj = new object();
        private readonly ILog _log;
        private readonly Timer _timer;
        private readonly FileSystemWatcher _watcher;
        private IDictionary<string, EventArgument> _changedFileList;

        /// <summary>
        /// </summary>
        public LazyFileSystemWatcher(string path, string filter, WatcherChangeTypes types) {
            _log = LogManager.GetLogger(GetType());

            _changedFileList = new SortedDictionary<string, EventArgument>();

            _timer = new Timer(TimerCallback, _changedFileList, -1, -1);
            _watcher = new FileSystemWatcher(path, filter);
            if ((types & WatcherChangeTypes.Created) == WatcherChangeTypes.Created)
                _watcher.Created += OnChanged;
            if ((types & WatcherChangeTypes.Renamed) == WatcherChangeTypes.Renamed)
                _watcher.Renamed += OnChanged;
            if ((types & WatcherChangeTypes.Changed) == WatcherChangeTypes.Changed)
                _watcher.Changed += OnChanged;
            if ((types & WatcherChangeTypes.Deleted) == WatcherChangeTypes.Deleted)
                _watcher.Deleted += OnChanged;
        }

        /// <summary>
        /// 监视是否包括子目录
        /// </summary>
        public bool IncludeSubdirectories {
            get { return _watcher.IncludeSubdirectories; }
            set { _watcher.IncludeSubdirectories = value; }
        }

        /// <summary>
        /// 是否允许事件冒泡
        /// </summary>
        public bool EnableRaisingEvents {
            get { return _watcher.EnableRaisingEvents; }
            set { _watcher.EnableRaisingEvents = value; }
        }

        /// <summary>
        /// 监视对象发生冒泡后空闲超事时触发的事件。
        /// 同一对象的Changed事件将串行化触发；不同文件的变化将同步多次触发Changed事件。
        /// </summary>
        public event Handler Changed;

        private void TimerCallback(object state) {
            IDictionary<string, EventArgument> list;
            lock (_lockObj) {
                list = _changedFileList;
                _changedFileList = new SortedDictionary<string, EventArgument>();
            }

            try {
                foreach (var argument in list.Values) {
                    Changed(this, argument);
                }
            } catch (Exception e) {
                _log.Warn(e);
            }
        }

        private void OnChanged(object sender, FileSystemEventArgs e) {
            lock (_lockObj) {
                EventArgument args;
                if (!_changedFileList.TryGetValue(e.Name, out args)) {
                    args = new EventArgument(e.FullPath, e.Name, e.ChangeType);
                    _changedFileList.Add(e.Name, args);
                } else {
                    args.History.AddLast(e.ChangeType);
                }
            }

            _timer.Change(300, -1);
        }

        #region Nested type: EventArgument

        /// <summary>
        /// LazyFileSystemWatch.Changed事件参数
        /// </summary>
        public class EventArgument : EventArgs {
            internal EventArgument(string fullPath, string name, WatcherChangeTypes type) {
                FullPath = fullPath;
                Name = name;
                History = new LinkedList<WatcherChangeTypes>();
                History.AddFirst(type);
            }

            /// <summary>
            /// 监视的目录
            /// </summary>
            public string FullPath { get; private set; }

            /// <summary>
            /// 目标文件
            /// </summary>
            public string Name { get; private set; }

            /// <summary>
            /// 冒泡事件清单
            /// </summary>
            public LinkedList<WatcherChangeTypes> History { get; private set; }
        }

        #endregion
    }
}